package com.wuliang.generate.response.author.configs;

import com.wuliang.generate.common.core.filter.JwtAuthenticationFilter;
import com.wuliang.generate.common.core.filter.JwtAuthorizationFilter;
import com.wuliang.generate.common.core.response.Result;
import com.wuliang.generate.common.core.response.ResultUtils;
import com.wuliang.generate.common.core.response.ReturnCode;
import com.wuliang.generate.common.core.utils.ClassUtils;
import com.wuliang.generate.bean.entities.author.AuthorUser;
import com.wuliang.generate.repositories.author.AuthorUserRepository;
import com.wuliang.generate.bean.vo.author.AuthorUserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Optional;

/**
 * Security 框架配置
 * @author WuLiang
 */
@Slf4j
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * 用户持久层对象
     */
    @Autowired
    private AuthorUserRepository authorUserRepository;

    MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    /**
     * 对HTTP协议的请求进行安全配置
     *
     * @param http {@link HttpSecurity}
     * @throws Exception 主要会抛出无权限异常以及认证失败异常 {@see org.springframework.security.core.AuthenticationException} {@see
     *                   org.springframework.security.access.AccessDeniedException}
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors().and().csrf().disable();

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.httpBasic().realmName("Realm");

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()));

        http.authorizeRequests()
            .antMatchers("/**")
            .permitAll()
//            .anyRequest()
//            .authenticated()
        ;

        http.formLogin().disable();

       /* http.rememberMe().rememberMeServices(new TokenBasedRememberMeServices("token", userDetailsService()));*/

        http.logout().permitAll();

        http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
            response.addHeader("Content-Type", "application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write(ResultUtils.accessError().toString());
        }).authenticationEntryPoint((request, response, authException) -> {
            log.error(authException.getMessage(), authException.getCause());
            Result result = new Result();
            ReturnCode code = ClassUtils.getReturnCodeByFieldName(authException.getClass().getSimpleName());
            result.setReturnCode(code == null? ReturnCode.SYSTEM_EXCEPTION.getCode() : code.getCode());

            try {
                result.setMessage(messages.getMessage(authException.getMessage(), Locale.SIMPLIFIED_CHINESE));
            } catch (NoSuchMessageException e) {
                result.setMessage(authException.getMessage());
            }
            if (authException instanceof InsufficientAuthenticationException) {
                result.setMessage("授权过期，请重新登录。");
                result.setReturnCode(ReturnCode.CREDENTIALS_EXPIRED.getCode());
            }
            response.addHeader("Content-Type", "application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write(result.toString());
        });
    }

    /**
     * 对认证方法的配置
     *
     * @param auth 认证方法工厂类
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
        auth.eraseCredentials(false);
    }

    /**
     * 对页面进行安全配置
     *
     * @param web 页面
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring().antMatchers("/*.html", "/webjars/**", "/*.css", "/*.js");
    }

    /**
     * 声明认证管理的Bean 方便在其余地方使用
     *
     * @return 认证管理
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 密码混淆配置
     *
     * @return 密码混淆工具
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(4);
    }

    /**
     * 认证成功后的处理方法，可删。
     *
     * @return {@link SavedRequestAwareAuthenticationSuccessHandler}
     */
    @Bean
    @Deprecated
    public AuthenticationSuccessHandler successHandler() {
        return new SavedRequestAwareAuthenticationSuccessHandler();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return username -> {
            Optional<AuthorUser> userOptional = authorUserRepository
                    .findOne(AuthorUserVo.builder().userUsername(username).build().toSpecification());
            if (!userOptional.isPresent()) {
                throw new UsernameNotFoundException(username + "尚未注册，请注册后登陆");
            }
            AuthorUser user = userOptional.get();
            Collection<GrantedAuthority> authorities = new HashSet<>();
            user.getPermissions().forEach(authorPermission -> authorities
                    .add(new SimpleGrantedAuthority(authorPermission.getAuthPermissionName().toUpperCase())));
            return User.builder()
                       .password(user.getUserPassword())
                       .username(username)
                       .authorities(authorities)
                       .build();
        };
    }
}
